if (typeof jQuery === 'undefined') {
	throw new Error("JSCalculator requires jQuery");
}

// sec for timer
var sec = 0;

function tick() {
	$('#timer').text(++sec);
}

(function() {
	'use strict';

	// merge b[] into a[]
	function optionsExtend(a, b) {
		for (var key in b) {
			a[key] = b[key];
		}
		return a;
	}

	// returns a matrix containing map info
	function genMat(avoidI, avoidJ, options) {

		// init matrix
		var matrix = new Array(options.height);
		for (var i = 0; i < matrix.length; i++) {
			var row = new Array(options.width);
			for (var j = 0; j < row.length; j++) {
				row[j] = 0;
			}
			matrix[i] = row;
		}

		// set mines
		var count = 0,
			dirx = [-1, 0, 1, -1, 1, -1, 0, 1],
			diry = [-1, -1, -1, 0, 0, 1, 1, 1];
		while (count < options.mines) {
			var i = Math.floor(options.height * Math.random()),
				j = Math.floor(options.width * Math.random()),
				dist = (i - avoidI) * (i - avoidI) + (j - avoidJ) * (j - avoidJ);

			// avoid initial cell
			if (!options.easyStart && dist == 0) {
				continue;
			}

			// if easyStart, ensure initial cell empty
			if (options.easyStart && dist <= 2) {
				continue;
			}

			// if cell is number, set a bomb
			if (matrix[i][j] < 9) {
				matrix[i][j] = 9;
				count++;

				// update surrounding cells
				for (var k = 0; k < 8; k++) {
					var ni = i + diry[k],
						nj = j + dirx[k];

					// ignore if out of bounds
					if (ni < 0 || ni >= options.height || nj < 0 || nj >= options.width) {
						continue;
					}
					if (matrix[ni][nj] < 8) {
						matrix[ni][nj] ++;
					}
				}
			}
		}
		return matrix;
	}

	function Minesweeper(jquery, options) {
		this._jquery = jquery;
		this._levels = {
			hard: {
				width: 30,
				height: 16,
				mines: 99,
			},
			normal: {
				width: 20,
				height: 12,
				mines: 50,
			},
			easy: {
				width: 9, // 8-30, 9 as default
				height: 9, // 8-16, 9 as default
				mines: 10, // smart generate to be implemented
			}
		}
		this._options = {
			// width: 9, 
			// height: 9, 
			// mines: 10, 
			easyStart: true, // if set true, ensure initial cell empty
			cell: 35, // size of cell (px)
		};
		this._timer = {
			timer: null,
			resetTimer: function() {
				this.stopTimer();
				sec = 0;
				$('#timer').text(0);
			},
			startTimer: function() {
				this.timer = setInterval('tick(window)', 1000);
			},
			stopTimer: function() {
				clearInterval(this.timer);
			}
		};
		this._init(options);
	}

	Minesweeper.prototype._init = function(options) {
		var o = options == 'hard' ? this._levels.hard : options == 'normal' ? this._levels.normal : options == 'easy' ? this._levels.easy : options;
		this._options = optionsExtend(optionsExtend(this._options, this._levels.easy), o); // merge user options into default
		this._jquery.empty();
		this._wrapper = $('<div class="minesweeper-wrapper"></div>');
		this._initPanel();
		this._wrapper.appendTo(this._jquery);
		$('.cell').css({
			height: this._options.cell + 'px',
			width: this._options.cell + 'px'
		});
		this._restart();
	}

	Minesweeper.prototype._genMatrix = function(i, j) {
		this._matrix = genMat(i, j, this._options);
		$('.cell').removeClass('init'); // .cell.init has a longer color transition time
		$('#reset').removeClass('disabled');
	}

	Minesweeper.prototype._initPanel = function() {
		var self = this;

		// init game panel
		self._panel = $('<div class="minesweeper-panel"></div>');
		var table = $('<table></table>');

		// init toolbar with a timer, a reset button and a flag counter
		var thead = $('<thead></thead>'),
			toolbar = $('<tr></tr>'),
			timer = $('<th class="text-center"></th>'),
			reset = $('<th></th>'),
			flags = $('<th class="text-center"></th>'),
			placeholder = $('<th></th>'),
			wTimer = 3,
			wFlags = 3,
			wReset = this._options.width % 2 ? 3 : 2,
			wPlaceholder = (this._options.width - wTimer - wFlags - wReset) / 2;

		timer.attr('colspan', wTimer).append($('<i class="fa fa-clock-o fa-fw"></i>')).append($('<span id="timer"></span>').text(0));

		var resetBtn = $('<button class="btn btn-warning btn-block disabled" id="reset"></button>');
		reset.attr('colspan', wReset).append(resetBtn.append($('<i class="fa fa-smile-o fa-lg"></i>'))
			.click(function() {
				self._timer.stopTimer();
				self._restart();
			})
		);

		flags.attr('colspan', wFlags).append($('<i class="fa fa-flag fa-fw"></i>')).append($('<span id="flags"></span>').text(0));

		if (wPlaceholder) {
			placeholder.attr('colspan', wPlaceholder);
		} else {
			placeholder = $();
		}

		toolbar.append(timer)
			.append(placeholder.clone())
			.append(reset)
			.append(placeholder.clone())
			.append(flags)
			.appendTo(thead);

		// init gamefield
		var tbody = $('<tbody></tbody>').append($('<tr></tr>').append($('<td></td>').attr('style', 'height: 5px')));
		self._cells = new Array(this._options.height); // array of PanelButton
		for (var i = 0; i < self._options.height; i++) {
			var tr = $('<tr></tr>'),
				row = new Array(self._options.width);
			for (var j = 0; j < self._options.width; j++) {
				var td = $('<td></td>'),
					panelBtn = new PanelButton(this, i, j),
					btn = panelBtn._el;
				row[j] = panelBtn;
				td.append(btn).appendTo(tr);
			}
			self._cells[i] = row;
			tr.appendTo(tbody);
		}

		self._panel.append(table.append(thead).append(tbody)).appendTo(self._wrapper)
			.mousedown(function(e) {
				if (e.which == 1) {
					self._leftDown = true;
				}
			}).mouseup(function(e) {
				if (e.which == 1) {
					self._leftDown = false;
				}
			});

		// init setting panel
		self._settings = $('<div class="minesweeper-setting center-block"></div>');
		var width = $('<input id="width" class="form-control">').val(self._options.width),
			height = $('<input id="height" class="form-control">').val(self._options.height),
			mines = $('<input id="mines" class="form-control">').val(self._options.mines),
			easyStart = $('<input type="checkbox" id="easyStart">').prop('checked', self._options.easyStart),
			apply = $('<button class="btn btn-success btn-block"></button>').text('重置游戏'),
			cell = $('<input id="cell" class="form-control">').val(self._options.cell),
			resize = $('<button class="btn btn-success btn-block"></button>').text('设置大小');
		self._settings.append(
			$('<fieldset></fieldset>').append(
				$('<legend></legend>').text('游戏设置')
			).append(
				$('<div class="row"></div>').append(
					$('<div class="col-xs-4"></div>').append(
						$('<button class="btn btn-warning btn-sm btn-block"></button>').text('简单').click(function() {
							$('#width').val(self._levels.easy.width);
							$('#height').val(self._levels.easy.height);
							$('#mines').val(self._levels.easy.mines);
						})
					)
				).append(
					$('<div class="col-xs-4"></div>').append(
						$('<button class="btn btn-warning btn-sm btn-block"></button>').text('普通').click(function() {
							$('#width').val(self._levels.normal.width);
							$('#height').val(self._levels.normal.height);
							$('#mines').val(self._levels.normal.mines);
						})
					)
				).append(
					$('<div class="col-xs-4"></div>').append(
						$('<button class="btn btn-warning btn-sm btn-block"></button>').text('困难').click(function() {
							$('#width').val(self._levels.hard.width);
							$('#height').val(self._levels.hard.height);
							$('#mines').val(self._levels.hard.mines);
						})
					)
				)
			).append(
				$('<table></table>').append(
					$('<thead></thead>').append(
						$('<tr></tr>').append(
							$('<th></th>').append(
								$('<label for="width"></label>').text('宽度')
							)
						).append(
							$('<th></th>').append(
								$('<label for="height"></label>').text('高度')
							)
						).append(
							$('<th></th>').append(
								$('<label for="mines"></label>').text('地雷')
							)
						).append(
							$('<th></th>').append(
								$('<label for="easyStart"></label>').text('EasyStart')
							)
						).append(
							$('<th></th>').append(
								$('<label for="cell"></label>').text('网格')
							)
						)
					)
				).append(
					$('<tbody></tbody>').append(
						$('<tr></tr>').append(
							$('<td></td>').append(width)
						).append(
							$('<td></td>').append(height)
						).append(
							$('<td></td>').append(mines)
						).append(
							$('<td></td>').append(easyStart)
						).append(
							$('<td></td>').append(cell)
						)
					).append(
						$('<tr></tr>').append(
							$('<td></td>').attr('colspan', 4).append(
								apply.click(function() {
									var w = $('#width').val(),
										h = $('#height').val(),
										m = $('#mines').val(),
										e = $('#easyStart').prop('checked'),
										options = {
											width: w > 30 ? 30 : w < 8 ? 8 : parseInt(w),
											height: h > 16 ? 16 : h < 8 ? 8 : parseInt(h),
											mines: parseInt(m),
											easyStart: e,
										};
									self._init(options);
								})
							)
						).append(
							$('<td></td>').append(
								resize.click(function() {
									var c = $('#cell').val(),
										ce = c > 45 ? 45 : c < 25 ? 25 : c;
									$('.cell').css({
										height: ce + 'px',
										width: ce + 'px'
									});
								})
							)
						)
					)
				)
			)
		).appendTo(self._wrapper);

	}

	Minesweeper.prototype._restart = function() {
		this._started = false;
		this._leftDown = false;
		this._matrix = [];
		for (var i = 0; i < this._cells.length; i++) {
			for (var j = 0; j < this._cells[i].length; j++) {
				var panelBtn = this._cells[i][j],
					btn = panelBtn._el;
				panelBtn._flagged = false;
				panelBtn._initEvents();
			}
		}
		this._left = this._options.height * this._options.width - this._options.mines;
		this._flags = this._options.mines;
		$('#flags').text(this._flags);
		this._timer.resetTimer();
		$('#reset button').addClass('disabled');
		$('#reset i').removeClass('fa-frown-o').addClass('fa-smile-o');
		setTimeout('$(".cell").removeClass("btn-default btn-danger btn-warning disabled").empty().addClass("btn-info");', 100);
	}

	Minesweeper.prototype._turn = function(i, j) {
		if (!this._started) {
			this._genMatrix(i, j);
			this._started = true;
			this._timer.startTimer();
		}
		var num = this._matrix[i][j],
			panelBtn = this._cells[i][j],
			btn = panelBtn._el;
		// flag clicked cells

		if (num == 9) { // mine cell
			this._lose(panelBtn);
			return;
		} else {
			btn.addClass('btn-default');
			if (num == 0) { // empty cell
				this._revealZeros(i, j);
			} else { // number cell
				this._revealAt(i, j);
			}
		}

		if (this._left <= 0) {
			this._win();
		}
	}

	Minesweeper.prototype._revealAt = function(i, j) {
		var self = this,
			panelBtn = self._cells[i][j],
			btn = panelBtn._el,
			num = self._matrix[i][j];
		if (num == 10 || panelBtn._flagged) {
			return;
		}
		var t = num > 8 || num <= 0 ? '' : num,
			tag = num > 8 ? $('<i class="fa fa-bomb"></i>') : $('<b></b>');
		btn.empty().append(tag.text(t));
		self._matrix[i][j] = 10;
		btn.removeClass('btn-info').addClass('btn-default')
			.off('click').off('mousedown').off('mouseup');
		if (t != '') {
			btn.mousedown(function(e) {
				if (e.which == 1) {
					panelBtn._leftDown = true;
				}
			}).mouseup(function(e) {
				if (e.which == 1) {
					panelBtn._leftDown = false;
				} else if (e.which == 3 && self._leftDown && self._turnSurroundings(i, j, num)) {
					btn.off('mousedown').off('mouseup');
				}
			});
		}
		self._left--;
	}

	Minesweeper.prototype._turnSurroundings = function(i, j, num) {
		var self = this,
			count = 0,
			dirx = [-1, 0, 1, -1, 1, -1, 0, 1],
			diry = [-1, -1, -1, 0, 0, 1, 1, 1],
			list = [];
		for (var k = 0; k < 8; k++) {
			var ni = i + diry[k],
				nj = j + dirx[k];

			// ignore if out of bounds
			if (ni < 0 || ni >= self._options.height || nj < 0 || nj >= self._options.width) {
				continue;
			}
			if (self._cells[ni][nj]._flagged) {
				count++;
			} else if (self._matrix[ni][nj] != 10) {
				list.push({
					i: ni,
					j: nj
				});
			}
		}
		if (count == num) {
			for (var k = 0; k < list.length; k++) {
				self._turn(list[k].i, list[k].j);
			}
			return true;
		}
		return false;
	}

	// recursively reveal beside empty cells
	Minesweeper.prototype._revealZeros = function(i, j) {
		var num = this._matrix[i][j];
		this._revealAt(i, j);

		// stops if cell isn't empty
		if (num == 0) {
			var dirx = [-1, 0, 1, -1, 1, -1, 0, 1],
				diry = [-1, -1, -1, 0, 0, 1, 1, 1];
			for (var k = 0; k < 8; k++) {
				var ni = i + diry[k],
					nj = j + dirx[k];
				if (ni < 0 || ni >= this._options.height || nj < 0 || nj >= this._options.width) {
					continue;
				}
				this._revealZeros(ni, nj);
			}
		}
	}

	Minesweeper.prototype._revealAll = function() {
		for (var i = 0; i < this._options.height; i++) {
			for (var j = 0; j < this._options.width; j++) {
				var num = this._matrix[i][j],
					panelBtn = this._cells[i][j],
					btn = panelBtn._el;
				btn.addClass('disabled');
				if (panelBtn._flagged) {
					panelBtn._toggleFlag();
					if (num != 9) {
						btn.addClass('btn-warning');
					}
				}
				if (num != 10) {
					this._revealAt(i, j);
				}
				if (num == 9) btn.removeClass('btn-default');
			}
		}
	}

	Minesweeper.prototype._win = function() {
		this._timer.stopTimer();
		for (var i = 0; i < this._options.height; i++) {
			for (var j = 0; j < this._options.width; j++) {
				var num = this._matrix[i][j],
					panelBtn = this._cells[i][j],
					btn = panelBtn._el;
				if (num != 10 && !panelBtn._flagged) {
					panelBtn._toggleFlag();
				}
				btn.addClass('disabled');
			}
		}
	}

	Minesweeper.prototype._lose = function(panelBtn) {
		this._timer.stopTimer();
		$('#reset i').removeClass('fa-smile-o').addClass('fa-frown-o');
		this._revealAll();
		panelBtn._el.removeClass('btn-default').addClass('btn-danger');
		// this._loseAnimation();
	}

	Minesweeper.prototype._loseAnimation = function() {
		var mask = $('<div class="minesweeper-mask"></div>"'),
			btn = $('<button class="btn btn-warning"></button>');
		mask.append(btn.text('RESTART')).appendTo($('body')).animate({
			opacity: 1
		}, 600, 'swing');
	}

	function PanelButton(minesweeper, i, j) {
		this._minesweeper = minesweeper;
		this._i = i;
		this._j = j;
		this._init();
	}

	PanelButton.prototype._init = function() {
		this._el = $('<button class="btn btn-default disabled init cell"></button>');
		this._flagged = false;
		this._initEvents();
	}
	PanelButton.prototype._initEvents = function() {
		var self = this;
		self._el.mousedown(function(e) { // handle right click event (flagging)
			if (e.which == 3) {
				self._el.off('mouseup').mouseup(function(e) {
					if (e.which == 3 && !self._minesweeper._leftDown) {
						self._toggleFlag();
					}
				});
			}
		}).click(function() {
			if (!self._flagged) {
				self._minesweeper._turn(self._i, self._j);
			}
		});
	}

	PanelButton.prototype._toggleFlag = function() {
		var self = this,
			btn = self._el;
		if (!self._flagged) { // to flag
			if (self._minesweeper._flags <= 0) {
				return;
			}
			btn.append($('<i class="fa fa-flag"></i>'));
			$('#flags').text(--self._minesweeper._flags);
		} else { // to unflag
			if (self._minesweeper._flags >= self._minesweeper._options.mines) {
				return;
			}
			btn.empty();
			$('#flags').text(++self._minesweeper._flags);
		}
		self._flagged = self._flagged ? false : true;
	}

	// make a jQuery plug-in
	$.prototype.minesweeper = function(options) {
		$(document).bind('contextmenu', function() {
			return false;
		});
		return new Minesweeper(this, options);
	}
})();